import asyncio
import logging
import os
import re
import tempfile
import time
import warnings
from configparser import ConfigParser
from typing import Optional, List

from flask import Flask, request, jsonify, Response
from langchain.text_splitter import RecursiveCharacterTextSplitter
from pydantic import BaseModel, Field

import chat
import ext
import ocr
import outline
import vector
from classify import extract_key_words, to_classify_new
from ext import process_content
from llm_service import generate_words
from markdown_split import MarkdownHeaderTextSplitter
from tpl_check import level_write
from tpl_write import reporter_write
from tpl_cw_with_cite import reporter_cw_cite
import entity
# import entity_all
import review
import origin
import description
app = Flask(__name__)

# --- 配置 ---
warnings.filterwarnings("ignore", category=UserWarning)
logging.basicConfig(format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", datefmt="%Y-%m-%d %H:%M:%S",
                    level=logging.INFO)
logger = logging.getLogger(__name__)


# 请求模型 (使用 Pydantic 保持类型提示，Flask 本身不需要)
class CheckRequest(BaseModel):
    desc: str = Field(..., description="审核要求描述")
    verifyText: str = Field(..., description="待审核文本内容")
    standardText: Optional[str] = Field("", description="审核标准文本（可选）")


# 响应模型
class CheckResponse(BaseModel):
    status: str = Field(..., description="处理状态：success/fail")
    result: List[dict] = Field(..., description="处理结果")

# --- 基础接口 ---
@app.route('/')
def hello_world():
    return 'Hello World!'


@app.route('/healthcheck', methods=['GET'])
def health_check():
    return jsonify({"code": 200, "msg": "OK", "data": None})


# --- vector.py 模块接口 ---
@app.route('/generate_embedding', methods=['POST'])
def embedding_endpoint():
    try:
        start_time = time.time()
        data = request.get_json()
        # 【修正】确保通过 vector.函数名 调用
        response = vector.embedding_model(data)
        logger.info(f"向量化处理完成，耗时 {time.time() - start_time:.2f}s")
        return jsonify(response)
    except Exception as e:
        logger.error(f"向量化API错误: {str(e)}", exc_info=True)
        return jsonify({"code": 500, "msg": str(e), "data": None}), 500


# 分块API
@app.route('/fragment_text', methods=['POST'])
def fragment_text():
    data = request.get_json()
    # 1-仅基于标题分割 (不设置 chunk_size) 效果: 生成的块数量较少，每个块对应一个最低级别的标题段落。块的大小可能非常不均匀，有些块可能非常大。代码块始终包含在它们所属的标题段落内。
    # 2-基于标题分割，然后根据段落分割，不考虑段落字符长度
    # 3-基于标题分割，然后根据段落分割，考虑段落字符长度，根据 chunk_size 和分隔符进一步细分
    # 效果: 首先按标题分割，然后对于超出 chunk_size 的块，
    #       会尝试在更自然的边界（如段落 `\n\n` 或句子/行 `\n`，以及其他标点）进行分割。
    #       目标是使块的非代码内容长度接近但不超过 chunk_size。
    #       代码块保持完整，并且其内容不计入 chunk_size 计算。
    #       这通常能产生大小更均匀、更适合后续处理（如 RAG）的块。
    splite_type = data['split_type']
    text = data["text"]
    chunk_response = []
    if splite_type == "1":
        splitter_no_limit = MarkdownHeaderTextSplitter()
        chunks_no_limit = splitter_no_limit.split_text(text)
        for chunk in chunks_no_limit:
            chunk_response.append({"metadata": chunk.metadata, "content": chunk.content})

    elif splite_type == "2":
        splitter_with_limit = MarkdownHeaderTextSplitter(chunk_size=150,
                                                         is_separator_regex=True)  # 注意添加 is_separator_regex=True 以使用默认中文分隔符
        chunks_with_limit = splitter_with_limit.split_text(text)
        for i, chunk in enumerate(chunks_with_limit):
            # non_code_len = splitter_with_limit._calculate_length_excluding_code(chunk.content)
            # print(f"Content Length (Total): {len(chunk.content)}")
            # print(f"Content Length (Non-Code): {non_code_len}") # 检查非代码长度是否接近 chunk_size
            # print(f"Metadata: {chunk.metadata}")
            # print("\n--- Markdown (With Metadata) ---")
            # print(chunk.to_markdown(return_all=True))
            # print("====" * 20) # 缩短分隔符以便查看更多块
            chunk_response.append({"metadata": chunk.metadata, "content": chunk.content})

    elif splite_type == "3":
        # Step 1: 先进行标题分割（不限制chunk_size）
        header_splitter = MarkdownHeaderTextSplitter()
        header_chunks = header_splitter.split_text(text)

        # Step 2: 对每个标题块进行智能分片
        def split_with_code_preservation(text, chunk_size=150):

            # 提取并替换代码块
            code_blocks = re.findall(r'```.*?```', text, flags=re.DOTALL)
            placeholders = [f"__CODE_BLOCK_{i}__" for i in range(len(code_blocks))]
            for i, code in enumerate(code_blocks):
                text = text.replace(code, placeholders[i], 1)

            # 自定义长度计算函数（排除代码块占位符长度）
            def length_func(txt: str) -> int:
                return len(re.sub(r'__CODE_BLOCK_\d+__', '', txt))

            # 创建递归分割器（支持正则表达式分隔符）
            text_splitter = RecursiveCharacterTextSplitter(
                chunk_size=chunk_size,
                chunk_overlap=0,
                length_function=length_func,
                separators=[
                    "\n\n",  # 段落
                    "\n",  # 换行
                    r"(?<=。)",  # 中文句号后
                    r"(?<=！)",  # 中文感叹号后
                    r"(?<=？)",  # 中文问号后
                    r"(?<=\.\s)",  # 英文句号加空格
                    r"(?<=\;\s)",  # 分号加空格
                    r"(?<=\,\s)",  # 逗号加空格
                ],
                is_separator_regex=True
            )

            # 执行分割
            chunks = text_splitter.split_text(text)

            # 还原代码块
            final_chunks = []
            for chunk in chunks:
                for i, ph in enumerate(placeholders):
                    chunk = chunk.replace(ph, code_blocks[i])
                final_chunks.append(chunk)

            return final_chunks

        # 处理每个标题块
        for header_chunk in header_chunks:
            sub_chunks = split_with_code_preservation(header_chunk.content, 300)
            for sub in sub_chunks:
                # 保留标题的元数据
                chunk_response.append({
                    "metadata": header_chunk.metadata,
                    "content": sub
                })


    elif splite_type == "4":
        # 严格按字符数 size 硬分割，支持 overlap
        size = data.get("size", 150)  # 默认150字符
        overlap = data.get("overlap", 0)  # 新增：默认重叠0字符
        # 校验 size
        if not isinstance(size, int) or size <= 0:
            return jsonify({"code": 400, "msg": "size must be a positive integer", "data": []})
        # 校验 overlap
        if not isinstance(overlap, int) or overlap < 0:
            return jsonify({"code": 400, "msg": "overlap must be a non-negative integer", "data": []})
        if overlap >= size:
            return jsonify({"code": 400, "msg": "overlap must be less than size", "data": []})

        # 硬分割逻辑（支持重叠）
        start = 0
        text_len = len(text)
        while start < text_len:
            end = start + size
            chunk_content = text[start:end]
            chunk_response.append({
                "metadata": {},  # 无结构化元数据
                "content": chunk_content
            })
            if end >= text_len:  # 最后一块，无需再移动
                break
            start = start + (size - overlap)  # 关键：控制重叠

    return jsonify({"code": 200, "msg": "ok", "data": chunk_response})


# Rerank API
@app.route('/rerank', methods=['POST'])
def rerank_endpoint():
    try:
        start_time = time.time()
        data = request.get_json()
        # 【修正】确保通过 vector.函数名 调用
        response = vector.rerank(data)
        logger.info(f"Rerank处理完成，耗时 {time.time() - start_time:.2f}s")
        return jsonify(response)
    except Exception as e:
        logger.error(f"Rerank API错误: {str(e)}", exc_info=True)
        return jsonify({"code": 500, "msg": str(e), "data": None}), 500


@app.route('/ocr', methods=['POST'])
def ocr_endpoint():
    """
    OCR API
    可处理图片、PDF和Word文档。
    它接收上传的文件，调用ocr.py中的process_document函数进行处理，
    并返回结构化的识别结果。
    """
    if 'file' not in request.files:
        return jsonify({"code": 400, "msg": "请求体中缺少'file'字段", "data": None}), 400

    file = request.files['file']
    if not file or file.filename == '':
        return jsonify({"code": 400, "msg": "未选择文件或文件名为空", "data": None}), 400

    start_time = time.time()
    temp_file_path = None
    try:
        # 为了安全地处理上传的文件，我们先将其保存为临时文件
        suffix = os.path.splitext(file.filename)[1]
        with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as temp_file:
            file.save(temp_file.name)
            temp_file_path = temp_file.name

        logger.info(f"文件已临时保存至: {temp_file_path}")

        # 调用 ocr.py 中新的、功能更强大的处理函数
        # 它直接接收文件路径作为参数
        result = ocr.process_document(temp_file_path)

        processing_time = time.time() - start_time
        logger.info(f"文档处理完成，耗时 {processing_time:.2f}s")

        # 检查ocr模块是否返回了错误信息
        if "error" in result:
            logger.error(f"处理文件时发生错误: {result['error']}")
            # 可以根据错误类型返回不同的状态码，这里统一用400
            return jsonify({"code": 400, "msg": result['error'], "data": None}), 400

        # 将处理结果封装在标准的API响应中
        return jsonify({"code": 200, "msg": "处理成功", "data": result})

    except Exception as e:
        logger.error(f"OCR API处理时发生未捕获的异常: {str(e)}", exc_info=True)
        return jsonify({"code": 500, "msg": f"服务器内部错误: {str(e)}", "data": None}), 500
    finally:
        # 无论成功或失败，都尝试删除临时文件
        if temp_file_path and os.path.exists(temp_file_path):
            try:
                os.unlink(temp_file_path)
                logger.info(f"临时文件已删除: {temp_file_path}")
            except Exception as e:
                logger.error(f"删除临时文件失败: {e}")


@app.route('/text_extract', methods=['POST'])
def text_extract():
    """处理纯文本提取 (调用 ext.py 中的 process_content)"""
    try:
        start_time = time.time()
        data = request.get_json()

        # 参数校验
        if not data:
            return jsonify({"code": 400, "msg": "请求体为空", "data": None})
        if 'text' not in data or 'instruction' not in data:
            return jsonify({"code": 400, "msg": "缺少必要参数 ('text' 和 'instruction')", "data": None})

        text = data['text']
        instruction = data['instruction']
        # 使用 .get() 获取参数并提供默认值，避免 KeyError
        output_format = data.get('output_format', 'json').lower()
        extract_fields = data.get('extract_fields')  # 可选字段

        # 检查输出格式是否支持 (与 ext 中的检查逻辑保持一致)
        supported_formats = ["json", "text", "markdown", "xml"]
        if output_format not in supported_formats:
            return jsonify({
                "code": 400,
                "msg": f"不支持的输出格式（可选：{'/'.join(supported_formats)}）",
                "data": None
            })

        # 整合指令
        final_instruction = instruction
        if extract_fields:
            final_instruction += f"。请提取以下字段：{extract_fields}"

        # 检查文本长度 (与 ext 中的检查逻辑保持一致)
        # if len(text) > 100000: # 10万字符限制
        #      return jsonify({
        #          "code": 400,
        #          "msg": "文本长度超过100000字符限制",
        #          "data": None
        #      })

        # !!! 关键修正：为 process_content 添加 blocks_info=None 参数
        # 添加异步事件循环处理（沿用原 app.py 的结构）
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
        result = loop.run_until_complete(process_content(
            text=text,
            instruction=final_instruction,
            blocks_info=None,  # <-- 添加缺失的 blocks_info 参数，对于纯文本设为 None
            output_format=output_format
        ))
        # result 已经是 ext.process_content 返回的字典结构 {code, msg, data, ...}

        logger.info(f"文本抽取处理完成，耗时 {time.time() - start_time:.2f}s")

        # 直接 jsonify process_content 的返回结果
        return jsonify(result)

    except Exception as e:
        logger.error(f"文本抽取API处理异常: {str(e)}", exc_info=True)
        # 返回与 ext.process_content 错误结构类似的响应
        return jsonify({
            "code": 500,
            "msg": f"服务器错误: {str(e)}",
            "data": {},
            "formatted_data": None,
            "locations": {},
            "is_supported_format": True  # 接口本身支持该格式，是处理出错
        })


# --- chat.py 模块接口 ---
def stream_helper(text: str, instruction: str, history: list):
    """辅助函数，统一处理流式生成逻辑"""
    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)
    async_gen = chat.process_simple_chat_stream(text, instruction, history)
    try:
        while True:
            try:
                chunk_json = loop.run_until_complete(async_gen.__anext__())
                yield f"data: {chunk_json}\n\n"
            except StopAsyncIteration:
                break
    finally:
        loop.close()
        asyncio.set_event_loop(None)


def get_stream_headers():
    """返回标准的SSE头"""
    return {
        "Content-Type": "text/event-stream",
        "Cache-Control": "no-cache",
        "Connection": "keep-alive",
        "X-Accel-Buffering": "no"
    }


@app.route('/chat', methods=['POST'])
async def chat_endpoint():
    """处理流式问答请求 (RAG)"""
    try:
        data = request.get_json()
        if not data or 'instruction' not in data:
            return jsonify({"code": 400, "msg": "请求体为空或缺少'instruction'参数", "data": None}), 400

        # 处理 text 字段，使其可以接收字符串或字符串列表
        text_input = data.get('text', '')
        combined_text = ""
        if isinstance(text_input, list):
            # 如果是列表，将所有字符串用换行符合并
            combined_text = "\n\n".join(str(item) for item in text_input)
        elif isinstance(text_input, str):
            # 如果是字符串，直接使用
            combined_text = text_input

        instruction = data.get('instruction')

        # 转换 history 格式
        history_converted = []
        for item in data.get('history', []):
            if "instruction" in item: history_converted.append({"role": "user", "content": item["instruction"]})
            if "response" in item: history_converted.append({"role": "assistant", "content": item["response"]})

        logger.info(f"开始处理 /chat 请求，instruction: '{instruction}'")
        # 将合并后的 combined_text 传递给 stream_helper
        return Response(stream_helper(combined_text, instruction, history_converted), headers=get_stream_headers())

    except Exception as e:
        logger.error(f"/chat API异常: {str(e)}", exc_info=True)
        return jsonify({"code": 500, "msg": f"服务器内部错误: {str(e)}", "data": None}), 500


@app.route('/classify', methods=['POST'])
def classify_text_endpoint():
    try:
        start_time = time.time()
        data = request.get_json()

        if not data:
            return jsonify({"code": 400, "msg": "请求体为空", "data": None}), 400

        # 全文信息
        text_content = data.get('text')
        # 多个体系节点使用;拼接
        classify = data.get('classify')
        # 是否多分类
        is_multi = data.get('duplicate')

        if not text_content:
            return jsonify({"code": 400, "msg": "缺少必要参数 ('text')", "data": None}), 400

        if not classify:
            return jsonify({"code": 400, "msg": "缺少必要参数 ('classify')", "data": None}), 400

        # 调用分类服务
        # category = classify_text(text_content, classify)
        # 模型抽取关键词
        ext_key_words = extract_key_words(text_content)
        logger.info(f"文本分类处理完成，耗时 {time.time() - start_time:.2f}s")
        logger.info(f"extract_key_words: {ext_key_words}")

        # cates = to_classify(ext_key_words, classify, is_multi)
        cates = to_classify_new(ext_key_words, classify, is_multi)
        logger.info(f"分类结果：{cates}")

        # 根据分类结果返回相应的响应
        if cates and isinstance(cates, str) and cates.strip():
            return jsonify({
                "code": 200,
                "msg": "分类成功",
                "data": {"category": cates}
            })
        else:
            logger.warning("文本分类未返回有效结果")
            return jsonify({
                "code": 202,
                "msg": "分类未返回有效结果",
                "data": {"category": ""}
            })

    except Exception as e:
        logger.error(f"文本分类API错误: {str(e)}", exc_info=True)
        return jsonify({"code": 500, "msg": f"服务器内部错误: {str(e)}", "data": None}), 500


# 报告大纲指令
def fix_outline_instruction(text):
    tmpl = """请为#instruction#领域生成一份结构清晰的报告文档大纲，要求如下：  
1. **层级限制**：严格遵循三级标题结构（1→1.1→1.1.1），不超过三级，同级标题个数2-5个不等  
2. **专业性**：章节标题需符合该领域的核心议题和技术/业务逻辑  
3. **内容覆盖**：包含但不限于行业背景、关键技术、应用场景、挑战与趋势，标题内容要求全面  
4. **格式范例**：  

**示例大纲参考结构**：  
1. 行业概述  
   1.1 领域定义与发展历程  
   1.2 当前市场规模与主要参与者  
2. 关键技术分析  
   2.1 核心技术1（如'深度学习在诊断中的应用'）  
      2.1.1 技术原理与优势  
   2.2 核心技术2  
3. 典型应用场景  
   3.1 场景1（如'医学影像识别'）  
      3.1.1 具体案例与成效  
4. 面临挑战  
   4.1 技术瓶颈  
   4.2 伦理与隐私问题  
5. 未来发展趋势  

请根据上述要求生成[#instruction#]的报告大纲，确保逻辑连贯且具备行业针对性，只要大纲不要追加其他内容。"""
    return tmpl.replace('#instruction#', text)


@app.route('/generate_outline', methods=['POST'])
async def generate_outline_endpoint():
    try:
        # 使用正确的依赖检查
        if chat.CHAT_API_URL is None:
            return jsonify({"code": 503, "msg": "服务未就绪 (问答模型客户端未初始化)", "data": None}), 503
        data = request.get_json()
        if not data or 'instruction' not in data:
            return jsonify({"code": 400, "msg": "缺少'instruction'参数", "data": None}), 400
        instruction = fix_outline_instruction(data['instruction'])
        return Response(stream_helper(text="", instruction=instruction, history=[]), headers=get_stream_headers())
    except Exception as e:
        logger.error(f"/generate_outline API异常: {str(e)}", exc_info=True)
        return jsonify({"code": 500, "msg": f"服务器内部错误: {str(e)}", "data": None}), 500


def fix_report_instruction(outline, title):
    tmpl = """你是一位顶尖的行业分析师，你的任务是根据下方提供的大纲，撰写一份关于“#title#”的深度研究报告。这份报告需要展现出极高的专业水准、深度见解和全面的信息。

请严格按照以下大纲和撰写要求进行工作：

**报告大纲：**
#outline#

**撰写要求：**
1.  **深度与篇幅要求**：对于大纲中的每一个一级标题，其内容**至少需要包含300字和3个以上的充实段落**。每个段落都应有清晰的主题句和详实的支撑论据，避免空泛的概括。
2.  **内容丰富度指令**：在撰写每个章节时，必须有意识地融合以下至少三种元素来丰富内容，使其具体且全面：
    * **背景与定义**：清晰地介绍该部分所涉及的核心概念、历史背景和其重要性。
    * **案例分析**：引用1-2个具体的现实案例、情景或实例来支撑你的观点。
    * **数据支撑**：如果可能，引用相关统计数据、研究发现或市场指标（如果没有实时数据，可以使用合理的示例性数据）。
    * **多维度探讨**：从不同角度（如：优势与劣势、机会与挑战、短期与长期影响）进行全面分析。
    * **未来趋势预测**：基于当前分析，对未来的发展趋势、可能出现的问题或潜在机遇进行预测。
3.  **结构与逻辑**：严格遵循大纲顺序，章节之间、段落之间逻辑清晰，衔接自然流畅。
4.  **专业口吻**：通篇使用客观、严谨、专业的分析师语言风格。

**重要要求：**
*内容一定要充足，不可简短的两三句话就完成一段内容

请从大纲的第一个部分开始，开始撰写这份详细的报告。"""

    tmpl = tmpl.replace("#title#", title).replace("#outline#", outline)
    return tmpl


# 根据大纲生成报告内容
@app.route('/generate_report', methods=['POST'])
async def generate_report_endpoint():
    try:
        # 【修正】使用正确的依赖检查
        if chat.CHAT_API_URL is None:
            return jsonify({"code": 503, "msg": "服务未就绪 (问答模型客户端未初始化)", "data": None}), 503
        data = request.get_json()
        if not data or 'instruction' not in data or 'text' not in data:
            return jsonify({"code": 400, "msg": "请求体缺少'instruction'(标题)或'text'(大纲)参数", "data": None}), 400
        instruction = fix_report_instruction(data['text'], data['instruction'])
        return Response(stream_helper(text="", instruction=instruction, history=[]), headers=get_stream_headers())
    except Exception as e:
        logger.error(f"/generate_report API异常: {str(e)}", exc_info=True)
        return jsonify({"code": 500, "msg": f"服务器内部错误: {str(e)}", "data": None}), 500


# 科研选题推荐
def fix_recommend_topic_instruction(domain: str, text: str) -> str:
    """为科研选题推荐功能，构建专用的指令(prompt)。"""
    return (
        f"根据用户输入的领域词和参考文本，理解领域词的含义和参考文本内容，进行推荐生成10个关联度高的专业性强的科研题目。"
        f"如果没有参考文本，请直接根据领域词输出10个关联度高的专业性强的科研题目。"
        f"\n领域词：{domain}"
        f"\n参考文本：{text}"
        f"\n要求：每个选题的长度在10-20个字之间，每个选题占一行以换行符结束。"
    )


@app.route('/recommend_tech_topic', methods=['POST'])
async def recommend_tech_topic():
    """推荐科研选题"""
    try:
        # 1. 【修正】使用正确的依赖检查
        if chat.CHAT_API_URL is None:
            logger.error("API请求失败: 问答模型服务未就绪。")
            return jsonify({
                "code": 503,
                "msg": "服务未就绪 (问答模型客户端未初始化)",
                "data": None
            }), 503

        # 2. 获取请求数据
        data = request.get_json()
        if not data or 'instruction' not in data:
            logger.error("API请求失败: 缺少 instruction 参数。")
            return jsonify({"code": 400, "msg": "请求体为空或缺少 instruction 参数", "data": None}), 400

        # 3. 准备参数并构建最终指令
        text = data.get('text', '')
        # 假设前端传来的'instruction'就是领域词(domain)
        domain = data.get('instruction')
        history = data.get('history', [])

        final_instruction = fix_recommend_topic_instruction(domain, text)

        # 4. 【简化】直接调用辅助函数处理流式响应
        return Response(stream_helper(text, final_instruction, history), headers=get_stream_headers())

    except Exception as e:
        logger.error(f"/recommend_tech_topic API异常: {str(e)}", exc_info=True)
        return jsonify({"code": 500, "msg": f"服务器内部错误: {str(e)}", "data": None}), 500


# 推荐相关词
@app.route('/recommend_words', methods=['POST'])
def recommend_words():
    try:
        data = request.get_json()

        if not data:
            return jsonify({"code": 400, "msg": "请求体为空", "data": None}), 400

        text_content = data.get('text')

        if not text_content:
            return jsonify({"code": 400, "msg": "缺少必要参数 ('text')", "data": None}), 400

        # 调用分类服务
        words = generate_words(text_content)
        return jsonify({"code": 200, "msg": "ok", "data": f"{words}"})

    except Exception as e:
        logger.error(f"生成相关词API错误: {str(e)}", exc_info=True)
        return jsonify({"code": 500, "msg": f"服务器内部错误: {str(e)}", "data": None}), 500


# 提取大纲
@app.route('/extract_outline', methods=['POST'])
def extract_outline_endpoint():
    """
    接收一个文章列表，调用模型为每篇文章提取层级大纲，并合并返回。
    """
    try:
        # 依赖检查：确保chat模块已初始化，因为outline模块会使用它
        if chat.CHAT_API_URL is None:
            return jsonify({"code": 503, "msg": "服务未就绪 (问答模型客户端未初始化)", "data": None}), 503

        articles = request.get_json()
        if not isinstance(articles, list):
            return jsonify({"code": 400, "msg": "输入必须是一个JSON数组", "data": None}), 400

        # 调用 outline.py 中的主处理函数
        outline_data = outline.extract_outline(articles)

        return jsonify({
            "code": 200,
            "data": outline_data,
            "msg": "操作成功"
        })

    except Exception as e:
        logger.error(f"/extract_outline API异常: {str(e)}", exc_info=True)
        return jsonify({"code": 500, "msg": f"服务器内部错误: {str(e)}", "data": None}), 500

# @app.route('/file_outline', methods=['POST'])
# def file_outline_endpoint():
#     """
#     处理文件上传(.docx, .pdf)，提取文本，调用模型获取层级化大纲，并返回。
#     """
#     try:
#         # 依赖检查
#         if chat.CHAT_API_URL is None:
#             return jsonify({"code": 503, "msg": "服务未就绪 (问答模型客户端未初始化)", "data": None}), 503
#
#         # 1. 检查请求中是否包含文件
#         if 'file' not in request.files:
#             return jsonify({"code": 400, "msg": "请求中未找到文件部分", "data": None}), 400
#
#         file = request.files['file']
#         if file.filename == '':
#             return jsonify({"code": 400, "msg": "未选择文件", "data": None}), 400
#
#         # 2. 检查文件类型是否支持
#         allowed_extensions = {'pdf', 'docx'}
#         file_ext = file.filename.rsplit('.', 1)[1].lower() if '.' in file.filename else ''
#         if file_ext not in allowed_extensions:
#             return jsonify({"code": 400, "msg": "文件类型不支持，请上传 .pdf 或 .docx 文件", "data": None}), 400
#
#         temp_file_path = None
#         try:
#             # 3. 将上传的文件安全地保存到临时位置
#             with tempfile.NamedTemporaryFile(delete=False, suffix=f".{file_ext}") as temp_file:
#                 file.save(temp_file.name)
#                 temp_file_path = temp_file.name
#
#             logger.info(f"文件已临时保存至: {temp_file_path}")
#
#             # 4. 调用 outline.py 中新的文件处理函数
#             outline_data = outline.extract_outline_from_file(temp_file_path)
#
#             return jsonify({
#                 "code": 200,
#                 "data": outline_data,
#                 "msg": "操作成功"
#             })
#
#         finally:
#             # 5. 无论成功或失败，都清理临时文件
#             if temp_file_path and os.path.exists(temp_file_path):
#                 os.remove(temp_file_path)
#                 logger.info(f"临时文件已删除: {temp_file_path}")
#
#     except Exception as e:
#         logger.error(f"/file_outline API异常: {str(e)}", exc_info=True)
#         return jsonify({"code": 500, "msg": f"服务器内部错误: {str(e)}", "data": None}), 500
#


# API端点 request: CheckRequest
@app.route('/check', methods=['POST'])
def check_endpoint():
    """
    基于大语言模型的文档审核与评分服务

    参数:
    - **desc**: 审核要求描述
    - **verifyText**: 待审核文本内容
    - **standardText**: 可选的审核标准文本

    返回:
    - **status**: 处理状态
    - **result**: 审核结果
    """
    data = request.get_json()
    if not data:
        return jsonify({"error": "Empty request body"}), 400

    try:
        result = level_write(
            data['desc'],
            data['verifyText'],
            data['standardText']
        )
        return jsonify({"status": "success", "result": result})
    except Exception as e:
        return jsonify({"status_code": 500, "detail": str(e)}), 400


# session: dict
@app.route('/level-write-stream', methods=['POST'])
def level_write_stream_endpoint():
    data = request.get_json()
    if not data:
        return jsonify({"error": "Empty request body"}), 400

    # 保持与原数据结构一致
    session = data.get('data') if isinstance(data, dict) else data
    session = {'data': session }
    # 确保 session 是字典类型
    if not isinstance(session, dict):
        return jsonify({"error": "Invalid data format"}), 400

    return reporter_write(session)


@app.route('/level-write-stream-cite', methods=['POST'])
def level_write_stream_cite_endpoint():
    data = request.get_json()
    if not data:
        return jsonify({"error": "Empty request body"}), 400

    # 保持与原数据结构一致
    session = data.get('data') if isinstance(data, dict) else data
    session = {'data': session }
    # 确保 session 是字典类型
    if not isinstance(session, dict):
        return jsonify({"error": "Invalid data format"}), 400
    return reporter_cw_cite(session)


# --- 实体类型关系提取接口 ---
@app.route('/entity_type', methods=['POST'])
def extract_entity_relationships_endpoint():
    """提取通用实体类型关系"""
    try:
        # 依赖检查应该针对 chat 模块的配置
        if chat.CHAT_API_URL is None:
            return jsonify({"code": 503, "msg": "服务未就绪 (聊天模型客户端未初始化)", "data": None}), 503

        data = request.get_json()
        if not data or 'text' not in data:
            return jsonify({"code": 400, "msg": "请求体为空或缺少'text'参数", "data": None}), 400

        text = data.get('text', '')
        instruction = data.get('instruction')

        results = entity.extract_entity_relationships_optimized(text, instruction)
        return jsonify({"code": 200, "msg": "操作成功", "data": results})

    except Exception as e:
        logger.error(f"/entity_type API异常: {str(e)}", exc_info=True)
        return jsonify({"code": 500, "msg": f"服务器内部错误: {str(e)}", "data": None}), 500

# 根据模板提取具体实体实例
@app.route('/entity_extraction', methods=['POST'])
async def extract_specific_entities_endpoint():
    try:
        # 依赖检查
        if chat.CHAT_API_URL is None:
            return jsonify({"code": 503, "msg": "服务未就绪 (聊天模型客户端未初始化)", "data": None}), 503

        data = request.get_json()
        if not data or 'text' not in data or 'instruction' not in data:
            return jsonify({"code": 400, "msg": "请求体为空或缺少'text'或'instruction'参数", "data": None}), 400

        input_text = data.get('text', '')
        instruction = data.get('instruction', '')
        more_type = data.get('more_type', False)

        # 调用重构后的函数，并直接接收其返回的完整字典
        analysis_results_dict = await entity.extract_specific_entities_concurrently(input_text, instruction, more_type)

        # 直接返回 entity 函数的结果
        return jsonify(analysis_results_dict)

    except Exception as e:
        logger.error(f"/entity_extraction API异常: {str(e)}", exc_info=True)
        return jsonify({"code": 500, "msg": f"服务器内部错误: {str(e)}", "data": None}), 500

@app.route('/type_des', methods=['POST'])
async def type_des_endpoint():
    try:
        # 依赖检查
        if chat.CHAT_API_URL is None:
            return jsonify({"code": 503, "msg": "服务未就绪 (问答模型客户端未初始化)", "data": None}), 503

        data = request.get_json()
        if not data or 'text' not in data:
            return jsonify({"code": 400, "msg": "请求体为空或缺少'text'参数", "data": None}), 400

        text = data.get('text', '')
        user_input = data.get('user_input')

        # 2. 调用时加上 await，并直接接收函数返回的字典
        results_dict = await entity.extract_entity_types_with_description(text, user_input)

        # 直接将 entity 函数返回的字典作为响应
        return jsonify(results_dict)

    except Exception as e:
        logger.error(f"/type_des API异常: {str(e)}", exc_info=True)
        return jsonify({"code": 500, "msg": f"服务器内部错误: {str(e)}", "data": None}), 500


#根据带描述的实体类型构建关系接口
@app.route('/type_rel', methods=['POST'])
async def type_rel_endpoint():  # <--- 1. 修改为 async def
    try:
        # 依赖检查
        if chat.CHAT_API_URL is None:
            return jsonify({"code": 503, "msg": "服务未就绪 (问答模型客户端未初始化)", "data": None}), 503

        data = request.get_json()
        if not data or 'instruction' not in data:
            return jsonify({"code": 400, "msg": "请求体为空或缺少必需的'instruction'参数", "data": None}), 400

        text = data.get('text', '')
        instruction = data.get('instruction', '')
        user_input = data.get('user_input')

        # 2. 调用时加上 await，并直接接收函数返回的字典
        results_dict = await entity.build_relationships_with_description(text, instruction, user_input)

        # 直接将 entity 函数返回的字典作为响应
        return jsonify(results_dict)

    except Exception as e:
        logger.error(f"/type_rel API异常: {str(e)}", exc_info=True)
        return jsonify({"code": 500, "msg": f"服务器内部错误: {str(e)}", "data": None}), 500

# @app.route('/entity_all', methods=['POST'])
# async def entity_all_endpoint():
#     """
#     接收长文本和可选的用户指令，并发提取所有实体、类型、描述和关系，并返回汇总结果。
#     """
#     try:
#         # 依赖检查
#         if chat.CHAT_API_URL is None:
#             return jsonify({"code": 503, "msg": "服务未就绪 (问答模型客户端未初始化)", "data": None}), 503
#
#         data = request.get_json()
#         if not data or 'text' not in data:
#             return jsonify({"code": 400, "msg": "请求体为空或缺少'text'参数", "data": None}), 400
#
#         input_text = data.get('text', '')
#         # 【新增】从请求中获取用户的自定义指令
#         instruction = data.get('instruction')
#
#         # 【更新】调用 entity_all.py 中的主处理函数，并传入 instruction
#         results = await entity_all.extract_all_entities_and_relationships(input_text, instruction)
#
#         return jsonify({
#             "code": 200,
#             "msg": "操作成功",
#             "data": results
#         })
#
#     except Exception as e:
#         logger.error(f"/entity_all API异常: {str(e)}", exc_info=True)
#         return jsonify({"code": 500, "msg": f"服务器内部错误: {str(e)}", "data": None}), 500
#
# @app.route('/more_type', methods=['POST'])
# async def more_type_endpoint():
#     """
#     接收文本和带描述的实体类型对，为每一对提取最多5个关系。
#     """
#     try:
#         # 依赖检查：确保chat模块已初始化F
#         if chat.CHAT_API_URL is None:
#             return jsonify({"code": 503, "msg": "服务未就绪 (问答模型客户端未初始化)", "data": None}), 503
#
#         data = request.get_json()
#         if not data or 'text' not in data or 'instruction' not in data:
#             return jsonify({"code": 400, "msg": "请求体为空或缺少'text'或'instruction'参数", "data": None}), 400
#
#         input_text = data.get('text', '')
#         instruction = data.get('instruction', '')
#
#         # 调用 entity_all.py 中新增的主处理函数
#         results = await entity_all.extract_multiple_relationships(input_text, instruction)
#
#         return jsonify({
#             "code": 200,
#             "msg": "操作成功",
#             "data": results
#         })
#
#     except Exception as e:
#         logger.error(f"/more_type API异常: {str(e)}", exc_info=True)
#         return jsonify({"code": 500, "msg": f"服务器内部错误: {str(e)}", "data": None}), 500

@app.route('/review', methods=['POST'])
async def review_endpoint():
    """
    根据用户指令，从文本中提取审核要点。
    """
    try:
        # 依赖检查
        if chat.CHAT_API_URL is None:
            return jsonify({"code": 503, "msg": "服务未就绪 (聊天模型客户端未初始化)", "data": None}), 503

        data = request.get_json()
        if not data or 'text' not in data or 'instruction' not in data:
            return jsonify({"code": 400, "msg": "请求体为空或缺少'text'或'instruction'参数", "data": None}), 400

        text = data.get('text', '')
        instruction = data.get('instruction', '')

        # 调用 review.py 中的主处理函数
        results_dict = await review.extract_review_points(text, instruction)

        # 直接返回 review.py 中已经格式化好的字典
        return jsonify(results_dict)

    except Exception as e:
        logger.error(f"/review API异常: {str(e)}", exc_info=True)
        return jsonify({"code": 500, "msg": f"服务器内部错误: {str(e)}", "data": None}), 500

@app.route('/review_point', methods=['POST'])
async def review_point_endpoint():
    """根据输入的审核点要求(instruction)，在输入的文本(text)中进行审核"""
    try:
        if chat.CHAT_API_URL is None:
            return jsonify({"code": 503, "msg": "服务未就绪 (聊天模型客户端未初始化)", "data": None}), 503

        data = request.get_json()
        if not data or 'text' not in data or 'instruction' not in data:
            return jsonify({"code": 400, "msg": "请求体为空或缺少'text'或'instruction'参数", "data": None}), 400

        text = data.get('text', '')
        instruction = data.get('instruction', '')

        results_dict = await review.audit_text_by_points(text, instruction)

        return jsonify(results_dict)
    except Exception as e:
        logger.error(f"/review_point API异常: {str(e)}", exc_info=True)
        return jsonify({"code": 500, "msg": f"服务器内部错误: {str(e)}", "data": None}), 500


# 实体消歧接口
@app.route('/entity_dis', methods=['POST'])
def entity_dis_endpoint():
    try:
        # 依赖检查
        if chat.CHAT_API_URL is None:
            return jsonify({"code": 503, "msg": "服务未就绪 (问答模型客户端未初始化)", "data": None}), 503

        data = request.get_json()
        if not data or 'user_input' not in data:
            return jsonify({"code": 400, "msg": "请求体为空或缺少'user_input'参数", "data": None}), 400

        user_input = data.get('user_input', '')

        # 调用 entity.py 中的新函数
        disambiguated_text = entity.disambiguate_entities(user_input)

        return jsonify({
            "code": 200,
            "msg": "操作成功",
            "data": disambiguated_text # 直接返回处理后的文本
        })

    except Exception as e:
        logger.error(f"/entity_dis API异常: {str(e)}", exc_info=True)
        return jsonify({"code": 500, "msg": f"服务器内部错误: {str(e)}", "data": None}), 500


# 溯源
@app.route('/origin', methods=['POST'])
def generate_origin_endpoint():
    if not request.is_json:
        return jsonify({
            "code": 400,
            "msg": "请求体格式错误，请使用JSON格式",
            "data": {}
        })
    input_data = request.get_json()

    try:
        result_data =origin.origin(input_data)

        return jsonify({
            "code": 200,
            "msg": "操作成功",
            "data": result_data
        })
    except (ValueError, TypeError) as e:
        # 捕获由 origin.py 抛出的输入数据格式或类型错误
        return jsonify({
            "code": 400, "data": {}, "msg": f"请求参数错误: {e}"
        }), 400

    except ConnectionError as e:
        # 捕获由 origin.py 抛出的调用外部 API 时的网络或服务错误
        return jsonify({
            "code": 500, "data": {}, "msg": f"服务内部错误: {e}"
        }), 500

    except Exception as e:
        # 捕获其他所有未知异常
        app.logger.error(f"发生未知错误: {e}")  # 在服务器日志中记录详细错误
        return jsonify({
            "code": 500, "data": {}, "msg": "服务发生未知错误，请联系管理员。"
        }), 500

# 描述
@app.route('/description', methods=['POST'])
def description_endpoint():
    try:
        data = request.get_json()
        instruction = data.get("instruction", "").strip()
        if not instruction:
            return jsonify({"code": 400, "msg": "缺少 instruction 参数", "data": None}), 400

        # 调用核心逻辑
        result_dict = asyncio.run(description.generate_description(instruction))

        return jsonify({
            "code": 200,
            "msg": "处理成功",
            "data": result_dict
        })

    except Exception as e:
        logger.error(f"/description 接口处理失败: {e}", exc_info=True)
        return jsonify({
            "code": 500,
            "msg": f"处理失败: {str(e)}",
            "data": None
        }), 500

if __name__ == '__main__':
    logger.info("应用程序启动：正在初始化服务...")

    config = ConfigParser()
    try:
        config_path = 'config.ini'
        if not os.path.exists(config_path):
            raise FileNotFoundError(f"在当前目录下未找到 {config_path} 文件。")
        config.read(config_path, encoding='utf-8')
        if not config.sections():
            raise ValueError(f"{config_path} 文件内容为空或格式不正确。")
        logger.info(f"{config_path} 文件加载成功。")

        # 1. 初始化 chat.py 中的所有模型服务
        chat.init_models(config)

        # 2. 初始化 ext.py 中的服务配置
        ext.init_ext(config)

        ocr.init_ocr(config)
    except Exception as e:
        logger.error(f"初始化失败: {e}", exc_info=True)
        exit()

    logger.info("所有服务初始化完成，准备启动Flask应用...")


    # 设置请求超时时间（秒）
    app.config['TIMEOUT'] = 600  # 10分钟超时

    app.run(host="0.0.0.0", port=5000, threaded=True)
